# Copyright (c) 2005,2006,2007 Dynamic Solutions Inc. (support@dynamic-solutions.com)
#
# This file is part of DataZoomer.
#
# DataZoomer is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# DataZoomer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""DataZoomer system object

The ZoomerSystem class uses the singleton (or Borg) pattern
to ensure there is only ever one instance of the
manager.  It is loaded as an object named zoomer.
"""


import os, sys, MySQLdb

from time import time
from os.path import join, isfile, isdir
from os import getcwd, chdir, environ

from dzdb import Database
from dzlog import EventLog
from dzconfig import Config
from dzsession import DzSession
from dzdebugger import DzDebugger
from dzerror import base_error_page, IniFileMissing, ThemeDoesNotExist

config_file_name = None
site_launcher = None

# This class is used as a subsitute for the real thing if the real thing is not passed.  (like during testing)
class dzWebVars:
    def __init__(self):
        pass
    def __getattr__(self,name):
        return ''
    def _clear(self):
        self.__dict__ = {}

class NoApp:
    name = 'noapp'

def GetEnvValue(name):
    if environ.has_key(name):
        return environ[name]
    else:
        return ''

if not '.' in sys.path:
    sys.path.append('.')

class ZoomerSystem:
    def __init__(self):
        self.name    = ''
        self.version = '0.1'

        (curdir,x) = os.path.split(os.path.abspath(__file__))
        (self.root_path,x) = os.path.split(curdir)

        # Read config file
        if config_file_name:
            config_file = config_file_name  # Set by dzlanuch
        else:
            config_file = '%s/dz.ini' % self.root_path  # this is the usual location.  If it's
                                                        # not here then it's up to the caller to
                                                        # set it.

        # Calculate physical path to web directory
        if site_launcher:
            (launcher,x) = os.path.split(site_launcher)
            self.site_path = os.path.abspath(launcher)
        else:
            self.site_path = os.path.join(self.root_path,'www')

        if not isfile(config_file): raise IniFileMissing()
        self.config = Config(config_file)

        self.start_time   = time()
        self.trace_time   = self.start_time
        self.ntuser       = int(self.config.get('site','ntuser',0))
        self.default_user = self.config.get('site','default_user','anonymous')
        self.loginid      = self.default_user
        self.app          = NoApp()
        self.sid          = 'nosid'
        self.authenticated = 0
        self.admin_group  = self.config.get('site','admin_group','admin')
        self.developer_group = self.config.get('site','developer_group','developers')

        self.app_path    = self.config.get('apps','path')
        self.app_paths   = [os.path.abspath(path) for path in self.app_path.split(';') if isdir(path)]

        # Connect database
        dbhost  = self.config.get('database','dbhost')
        dbuser  = self.config.get('database','dbuser')
        dbname  = self.config.get('database','dbname')
        dbpass  = self.config.get('database','dbpass')

        self.name = self.config.get('site','name')
        self.slogan = self.config.get('site','slogan','')

        self.db = Database(MySQLdb.Connect,host=dbhost,user=dbuser,passwd=dbpass,db=dbname)
        self.db.autocommit(1)

        # Attach a debugger
        self.debug   = DzDebugger(self.name)

        # Initialize session object
        self.session = DzSession(self)

        # Initialize webvars object
        self.webvars = dzWebVars()

        # Create Event Log Object
        log = EventLog(self)
        self.log = log

        # Save some useful URLs
        if GetEnvValue('HTTPS')=='on':
            default_protocol    = 'https'
            usual_port = '443'
        else:
            default_protocol    = 'http'
            usual_port = '80'

        actual_port = GetEnvValue('SERVER_PORT')
        if actual_port == usual_port:
            port_specifier = ''
        else:
            port_specifier = ':%s'%actual_port

        default_site_url    = '%s://%s%s' % (default_protocol,GetEnvValue('SERVER_NAME'),port_specifier)
        default_root_url    = ''
        t = GetEnvValue('SCRIPT_NAME')
        default_script_name = t.strip(t.split('/')[-1])
        if not default_script_name:
            default_script_name = '/index.py'

        self.site_path          = self.config.get('site','path',self.site_path)
        self.site_url           = self.config.get('site','url',default_site_url)
        self.site_owner         = self.config.get('site','owner_name','Dynamic Solutions Inc.')
        self.site_email         = self.config.get('site','owner_email','support@dynamic-solutions.com')
        self.main_url           = self.config.get('site','mainurl','%s%s' % (self.site_url,default_script_name)) # main application URL
        self.root_url           = self.config.get('site','rooturl',self.main_url.rsplit('/',1))[0]
        self.authentication_url = '%s?app=%s' % (self.main_url,self.config.get('apps','login','login'))

        self.use_cookies        = self.config.get('sessions','use_cookies','1').lower() in ['1','yes','y','T']
        self.destroy_sessions   = self.config.get('sessions','destroy','1').lower() in ['1','yes','y','T']

        self.home_app           = self.config.get('apps','home','home')
        self.index_app          = self.config.get('apps','index','content')

        # Initialize mail info
        self.smtp_domain        = self.config.get('mail','smtp_domain','datazoomer.com')
        self.smtp_host          = self.config.get('mail','smtp_host','')
        self.smtp_port          = self.config.get('mail','smtp_port','')
        self.smtp_user          = self.config.get('mail','smtp_user','')
        self.smtp_passwd        = self.config.get('mail','smtp_passwd','')
        self.from_addr          = self.config.get('mail','from_addr','')

        # Initialize graphing info
        self.input_path         = self.config.get('input','path',join(self.root_path,'input'))
        self.data_path          = os.path.abspath(self.config.get('data','path',join(self.root_path,'data')))

        # Initialize dictionaries for apps and themes
        self.loaded_apps = {}
        self.themes      = {}

        # Initialize theme
        self.theme_path = os.path.abspath(self.config.get('theme','path',join(self.site_path,'themes')))
        self.theme_name = self.config.get('theme','name','default')
        self.theme_url  = self.config.get('theme','url','%sthemes/' % self.main_url)

        # Debugging
        active = self.config.get('debug','mode', '0')
        self.debugging  = active in ['0','1','2'] and int(active) or 0
        self.trapping   = self.config.get('error','mode','0')
        self.support_email = self.config.get('error','bug_email','')
        self.error_page = base_error_page

        self.user_id = 2  # this is for debugging from the command line, it's overridden when dz runs
        self.MIN_USERID_SIZE = 4
        self.MIN_PASSWD_SIZE = 4

        self.printed_output = ''
        self.initialize_connection_state()

    def initialize_connection_state(self):
        """Initialize the state of the conneciton"""

        # Initialize session object
        self.session = DzSession(self)

        self.ip = GetEnvValue('REMOTE_ADDR')
        # NT username REMOTE_USER, AUTH_USER, LOGON_USER
        remote_user = GetEnvValue('REMOTE_USER')
        self.ntusername = (remote_user and remote_user or None)

        self.webvars._clear()

        # Assign environment variable
        self.environ = environ

        # Attach an out of process jobticket
        self.jobticket = None

        self.apps        = {}

    def show_debug_info(self):
        """Is the system configured to display debug information"""
        if (self.debugging==2) or (
            self.debugging==1 and self.authenticated and (
                self.acl.is_developer(self.loginid) or self.acl.is_member(self.loginid,self.admin_group) )):
            return 1
        return 0

    def trace(self,msg):
        newtime = time()
        try: self.trace_time
        except: self.trace_time = self.start_time
        self.debug('> %s: %2.4fs' % (msg,newtime - self.trace_time))
        self.trace_time = newtime

    def load_app(self,name):
        """Load an application into memory"""

        def load_app_module(pathname):
            namespace = {'appname':name,'zoomer':self,'logger':self.log}
            execfile(pathname,namespace)
            return namespace['result']

        # already loaded?
        if name in self.loaded_apps:
            return self.loaded_apps[name]

        # search the path
        for path in self.app_paths:
            pathname = os.path.join(path,name,'app.py')
            if isfile(pathname):
                import dzzapp
                return dzzapp.Zapp(os.path.join(path,name),name)

            pathname = os.path.join(path,name,'zapp.py')
            if isfile(pathname):
                # new style app
                import dzzapp
                return dzzapp.Zapp(os.path.join(path,name),name)

            else:
                # old style app
                pathname  = '%s/%s/%s.py' % (path,name,name)
                if isfile(pathname):
                    savedir = getcwd()
                    app_path = '%s/%s' % (path,name)
                    chdir(app_path)
                    app = load_app_module(pathname)
                    chdir(savedir)
                    if app:
                        app.path = app_path
                        return app

        return None

    def load_theme(self,name):
        """Load a theme into memory"""
        if name in self.themes:
            return self.themes[name]
        filename  = join(self.theme_path,name,'%s.py'%name)  #'%s/themes/%s/%s.py' % (self.site_path,name,name)
        if not isfile(filename):
            from attic.dztheme import Theme
            theme = Theme()
        else:
            namespace = {'themename':name}
            execfile(filename,namespace)
            theme = namespace['result']
        if theme:
            self.themes[name] = theme
            return theme
        else:
            print 'No theme found'

    def set_theme(self,name):
        self.theme = self.load_theme(name)

if __name__ == '__main__':
    class TestApp:
        def __init__(self):
            self.name = 'TestApp'
        def run(self):
            print 'This was written to stdout'
            manager.debug('output to debugger')
            manager.trace('here is a trace')
            return 'Successfully created and ran TestApp()'

    import unittest

    class ManagerTest(unittest.TestCase):
        def test_dzWebVars(self):
            webvars = dzWebVars()
            self.assert_(webvars.test == '')
            webvars.name = 'Frank'
            self.assert_(webvars.name == 'Frank')
            webvars._clear()
            self.assert_(webvars.name == '')
            self.assert_(webvars._clear)

        def test_Zoomer_init(self):
            zoomer = ZoomerSystem()

    unittest.main()
